from Tiles import *
import Controller

import random

_num_suns = [ 13 , 13 , 16 ]
_max_num_ras = [ 8 , 9 , 10 ]
def _init_suns( board , players ):
    num_players = len( players )
    num_suns = _num_suns[ num_players - 3 ]

    suns = []
    for sun_value in xrange( num_suns ):
        suns.append( Sun( sun_value + 1 ) )

    board_sun = suns.pop( 0 );

    hands = []
    for player in xrange( num_players ):
        hands.append( [] )

    #first hand out the suns that go
    for sun_idx in xrange( num_suns - 1 ):
        if num_suns - sun_idx - 1 == num_players:
            break

        hands[ sun_idx % num_players ].append( suns[ sun_idx ] )
    
    for player_idx in xrange( num_players ):
        hands[ player_idx ].append( suns[ -1 * player_idx - 1 ] )

    random.shuffle( hands )
    for i in xrange( num_players ):
        players[ i ]._suns = hands[ i ]

    board.acquire_sun( board_sun )

def _init_tiles():
    tile_bag = TileBag()

    for i in xrange( 8 ):
        tile_bag._tiles.append( GodTile() )

    for i in xrange( 5 ):
        tile_bag._tiles.append( GoldTile() )

    for i in xrange( 25 ):
        tile_bag._tiles.append( RiverTile() )

    for i in xrange( 12 ):
        tile_bag._tiles.append( FloodTile() )

    for i in xrange( 25 ):
        tile_bag._tiles.append( PharaohTile() )

    for i in xrange( 25 ):
        tile_bag._tiles.append( CivTile( i % 5 ) )

    for i in xrange( 40 ):
        tile_bag._tiles.append( MonumentTile( i % 8 ) )

    for i in xrange( 2 ):
        tile_bag._tiles.append( DroughtTile() )

    for i in xrange( 2 ):
        tile_bag._tiles.append( EarthquakeTile() )

    for i in xrange( 2 ):
        tile_bag._tiles.append( PestilenceTile() )

    for i in xrange( 4 ):
        tile_bag._tiles.append( FamineTile() )

    for i in xrange( 30 ):
        tile_bag._tiles.append( RaTile() )

    return tile_bag

class RaManager:
    def __init__( self ):
        pass

class Board:
    MAX_TILES = 8
    MAX_NUM_RAS = [ 8 , 9 , 10 ]
    def __init__( self , players ):
        self._sun = _init_suns( self , players )
        self._tiles = []
        self._ra_tiles = 0

	self._max_num_ras = Board.MAX_NUM_RAS[ len( players ) - 3 ]

    def __str__( self ):
	ret = 'Sun: %d\n%d Ra Tiles Out\n%s' % ( self._sun.value() , self._ra_tiles , str( self._tiles ) )
	return ret

    def __len__( self ):
	return len( self._tiles )

    def __contains__( self , tile ):
	return tile in self._tiles

    def __getitem__( self , idx ):
	return self._tiles[ idx ]

    def epoch_end( self ):
	self._tiles = []
	self._ra_tiles = 0

    def drew_ra( self ):
	self._ra_tiles += 1
	return self._ra_tiles == self._max_num_ras

    def number_of_ras( self ):
        return self._ra_tiles

    def place_tile( self , tile ):
        self._tiles.append( tile )

    def tiles( self ):
        return self._tiles

    def is_full( self ):
	return len( self._tiles ) == Board.MAX_TILES

    def sun( self ):
        return self._sun

    def acquire_sun( self , sun ):
	self._sun = sun

    def auction_won( self , winning_bidder , winning_sun ):
        winning_bidder.acquire_sun( self._sun )
        winning_bidder.drop_sun( winning_sun )

        self.acquire_sun( winning_sun )
        winning_bidder.acquire_tiles( self._tiles )
        self._tiles = []        

    def clear( self ):
	self._tiles = []
    
class Player:
    def __init__( self , name , controller ):
        '''A controller is a Player using standard input, connected over the network, 
        or an AI that's loaded in the program or connected over the network'''
	self._name = name
        self._controller = controller
	self._score = 0

        self._suns = []
        self._rivers = []
        self._floods = []
        self._gods = []
        self._civs = []
        self._monuments = []
        self._pharaohs = []
        self._golds = []
	self._destructive = []

	controller.set_player( self )

    def __str__( self ):
	ret = 'Player: ' + self._name + '\n'
	ret += str( self._suns )
	if self._rivers:
	    ret += '\n' + str( self._rivers )
	if self._floods:
	    ret += '\n' + str( self._floods )
	if self._gods:
	    ret += '\n' + str( self._gods )
	if self._civs:
	    ret += '\n' + str( self._civs )
	if self._monuments:
	    ret += '\n' + str( self._monuments )
	if self._pharaohs:
	    ret += '\n' + str( self._pharaohs )
	if self._golds:
	    ret += '\n' + str( self._golds )

	return ret

    def get_name( self ):
	return self._name

    def suns( self ):
	return self._suns

    def num_suns_left( self ):
        ret = 0
        for sun in self._suns:
            if sun.usable():
                ret += 1

        return ret

    def high_sun( self ):
	return max( self._suns )

    def sum_suns( self ):
        ret = 0
        for sun in self._suns:
            ret += sun.value()

        return ret

    def get_sun_for_value( self , bid_value ):
        for sun in self._suns:
            if sun.value() == bid_value:
                return sun

        raise Error()

    def rivers( self ):
        return self._rivers

    def floods( self ):
        return self._floods

    def gods( self ):
        return self._gods

    def civs( self ):
        return self._civs

    def monuments( self ):
        return self._monuments

    def pharaohs( self ):
        return self._pharaohs

    def golds( self ):
        return self._golds

    def acquire_sun( self , board_sun ):
        board_sun.acquired()
        self._suns.append( board_sun )

    def drop_sun( self , sun ):
        self._suns.remove( sun )

    def flip_suns( self ):
	for sun in self._suns:
	    sun.epoch_end()

    def acquire_tiles( self , tiles ):
        for tile in tiles:
            tile.add_to_player( self )

	for tile in self._destructive:
	    tile.destruct( self )

	self._destructive = []

    def action( self , board , players ):
        return self._controller.action( board , players )

    def bid( self , tiles , board_sun , bid_history , forced_to_bid ):
        return self._controller.bid( tiles , board_sun , bid_history , forced_to_bid )

    def score( self , players , epoch ):
        score = 0

        #Score pharaohs - 5 for having the most (or tied) and -2 for having the least (or tied).
        #Score nothing if everyone has the same
        for player in players:
            if len( player.pharaohs() ) > len( self.pharaohs() ):
                most_pharaohs = False
                break
        else:
            most_pharaohs = True

        for player in players:
            if len( player.pharaohs() ) < len( self.pharaohs() ):
                least_pharaohs = False
                break
        else:
            least_pharaohs = True

        if most_pharaohs != least_pharaohs:
            if most_pharaohs:
                score += 5
            else:
                score -= 2

        #Score Civs, number of unique colors
        unique_flavors = set( [] )
        for civ in self.civs():
            unique_flavors.add( civ.flavor() )

        score += CivTile._scores[ len( unique_flavors ) ]

        #Score Gold Tiles, 3 points per
        score += ( len( self.golds() ) * 3 )

        #Score God Tiles, 2 points per
        score += ( len( self.gods() ) * 2 )

        #Score 1 point for each flood and river as long as the player has at least one flood
        if len( self.floods() ):
            score += len( self.floods() ) + len( self.rivers() )

        if epoch != 3:
            return score

        #Score 5 points for the most suns, -5 for the least
        suns = self.sum_suns()
        for player in players:
            if player.sum_suns() > suns:
                most_suns = False
                break
        else:
            most_suns = True

        for player in players:
            if player.sum_suns() < suns:
                least_suns = False
                break
        else:
            least_suns = True

        if most_suns != least_suns:
            if most_suns:
                score += 5
            else:
                score -= 5

        #Score monuments - 1 point per unique monument, 10 points for 7, 15 for all 8
        #Also get 5 points for a three of a kind, 10 for a four of a kind, and 15 for all 5 of a kind
        monuments = {}
        for monument in self.monuments():
            if monument.flavor() not in monuments:
                monuments[ monument.flavor() ] = 0

            monuments[ monument.flavor() ] += 1

        score += MonumentTile._unique_scores[ len( monuments ) ]

        for identicals in monuments.values():
            score += MonumentTile._same_scores[ identicals ]

	self._score += score
	return score

    def wipe_tiles( self ):
        self._floods = []
        self._golds = []
        self._gods = []
        self._civs = []

class TileBag:
    def __init__( self ):
        self._tiles = []

    def draw( self ):
        tile = random.choice( self._tiles )
        self._tiles.remove( tile )
        
        return tile

    def __repr__( self ):
        return str( self._tiles )

class Sun:
    def __init__( self , value ):
        self._value = value
        self._usable = True #refers to whether this Sun is able to be used to bid for the remainder of the current epoch

    def __repr__( self ):
        return '%d%s' % ( self._value , self._usable == False and '(Used)' or '' )

    def __gt__( self , sun ):
	return self.value() > sun.value()

    def __lt__( self , sun ):
	return self.value() < sun.value()

    def usable( self ):
        return self._usable

    def value( self ):
        return self._value

    def acquired( self ):
        self._usable = False

    def epoch_end( self ):
        self._usable = True

class Draw:
    '''Drawing is an action that a player can take.  Implements the operate method
    that is called by the Ra Engine'''
    def __init__( self , player ):
	self._player = player

    def operate( self , board , tile_bag , players ):
        tile = tile_bag.draw()
	if isinstance( tile , RaTile ):
	    if board.drew_ra():
		return

	    ra = Ra( self._player , False )
	    ra.operate( board , tile_bag , players )
	else:
	    board.place_tile( tile )

class PlayGod:
    '''Playing a God tile is an action a player can take.  Implements the operate method
    that is called by the Ra Engine'''
    def __init__( self , player , tile_to_take ):
	self._player = player
	self._tile_to_take = tile_to_take

    def get_tile_to_take( self ):
	return self._tile_to_take

    def operate( self , board , tile_bag , players ):
	self._player.gods().pop()
	board.tiles().remove( self._tile_to_take )
	self._tile_to_take.add_to_player( self._player )

class Ra:
    '''Calling Ra is an action a player can take.  Implements the operate method
    that is called by the Ra Engine'''
    def __init__( self , player , forced ):
        self._player = player
        self._forced = forced

    def operate( self , board , tile_bag , players ):
        player_idx = players.index( self._player ) + 1
        bid_order = []
        for offset in xrange( len( players ) ):
            bid_order.append( players[ ( player_idx + offset ) % len( players ) ] )

	bid_history = BidHistory()
        for next_to_bid in bid_order:
            forced_to_bid = self.forced( next_to_bid , bid_history )
            bid = next_to_bid.bid( board.tiles() , board.sun() , bid_history , forced_to_bid )
	    bid_history.record( next_to_bid , bid )

        if bid_history.was_bidded_on() == False:
            return

        winning_bidder = bid_history.winning_bidder()
	print 'winning bidder is ' + winning_bidder.get_name()
        winning_sun = bid_history.winning_bid()

        board.auction_won( winning_bidder , winning_sun )

    def forced( self , next_to_bid , bid_history ):
        if self._forced == False:
            return False

        if next_to_bid != self._player:
            return False

        if bid_history.was_bidded_on():
            return False

        return True

class BidHistory:
    def __init__( self ):
	self._history = []
	self._winning_bid = None

    def __str__( self ):
	ret = 'Bids'
	for bid in self._history:
	    ret += '\n\t' + bid[0].get_name() + ' ' + str( bid[1] )
	return ret

    def record( self , bidder , bid ):
	bid_record = ( bidder , bid )
	self._history.append( bid_record )
	if bid is not None:
	    self._winning_bid = bid_record

    def was_bidded_on( self ):
	return self._winning_bid != None

    def winning_bidder( self ):
	if self._winning_bid == None:
	    return None

	return self._winning_bid[0]
	
    def winning_bid( self ):
	if self._winning_bid == None:
	    return None
	return self._winning_bid[1]

class PlayGodTile:
    def __init__( self , player , god_tile_used , tile_to_take ):
        self._player = player
        self._tile_to_take = tile_to_take
        self._god_tile_used = god_tile_used

    def operate( self , board , tile_bag , players ):
        self._player.remove_tile( self._god_tile_used )
        self._player.acquire_tiles( [ self._tile_to_take ] )

class RaEngine:
    def __init__( self , board , players ):
        self._board = board
        self._players = players
        self._tile_bag = _init_tiles()
	_init_suns( self._board , self._players )
        self._epoch = 0

    def epoch_finished( self ):
        players_left = 0
        for player in self._players:
            if player.num_suns_left() > 0:
                players_left += 1

        if players_left == 0:
            return True

        if _max_num_ras[ len( self._players ) - 3 ] == self._board.number_of_ras():
            return True

        return False

    def set_starting_player( self ):
        hi_sun = 0
        for player_idx in xrange( len( self._players ) ):
            player = self._players[ player_idx ]
            if player.high_sun().value() > hi_sun:
                hi_sun = player.high_sun().value()
                self._player_idx = player_idx

    def get_current_player( self ):
	return self._players[ self._player_idx ]

    def board_is_full( self , players_turn ):
	if self._board.is_full() == False:
	    return

	unforced_ra = Ra( players_turn , False )
	unforced_ra.operate( self._board , self._tile_bag , self._players )
	if self._board.is_full():
	    self._board.clear()

	self._player_idx += 1

    def play_epoch( self ):
        self._epoch += 1
        self.set_starting_player()

	turn_number = 0
        while self.epoch_finished() == False:
            player = self._players[ self._player_idx % len( self._players ) ]

	    if player.num_suns_left() == 0:
		self._player_idx += 1
		continue

	    if self.board_is_full( player ):
		continue

	    action = player.action( self._board , self._players )
	    action.operate( self._board , self._tile_bag , self._players )

            self._player_idx += 1

        for player in self._players:
            player.score( self._players , self._epoch )
            player.wipe_tiles()
            player.flip_suns()
	    for player_to_inform in self._players:
		player_to_inform._controller.inform_score( player.get_name() , player._score )

	self._board.epoch_end()

def play():
    players = []
    for i in xrange( 3 ):
	players.append( Player( str( i ) , Controller.Human() ) )

    board = Board( players )
    engine = RaEngine( board , players )
    engine.play_epoch()
